package ar.edu.itba.pod.legajo47573;

import java.rmi.AlreadyBoundException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import ar.edu.itba.balance.api.AgentsBalancer;
import ar.edu.itba.node.Node;
import ar.edu.itba.node.NodeInformation;
import ar.edu.itba.node.api.ClusterAdministration;


@SuppressWarnings("serial")
public class ClusterAdministrationImpl extends UnicastRemoteObject implements ClusterAdministration{

	private static final String GROUP_NAME = "inicio";
	private String groupName = null;
	private MyNode currentNode;
	private Set<NodeInformation> nodes = new HashSet<NodeInformation>();

	public ClusterAdministrationImpl(MyNode currentNode) throws RemoteException{
		super();
		this.currentNode = currentNode;

		try {
			currentNode.getRegistry().bind(Node.CLUSTER_COMUNICATION, this);
		} catch (AlreadyBoundException e) {
			e.printStackTrace();
		}
	}
	
	//TODO Borrar esto!!!
	public Set<NodeInformation> getKnownNodes(){
		System.out.println("Soy el nodo " + currentNode.getInfo().id());
		System.out.println("Mis nodos son");
		System.out.println(nodes);
		return nodes;
	}

	@Override
	public void createGroup() throws RemoteException {
		if(this.groupName != null)
			throw new IllegalStateException("El grupo ya fue creado");
		else
		{
			this.groupName = GROUP_NAME;
			System.out.println("Grupo " + GROUP_NAME + " creado");
		}

		this.currentNode.setCoordinator(currentNode.getInfo());
	}

	@Override
	public String getGroupId() throws RemoteException {
		return this.groupName;
	}

	@Override
	public boolean isConnectedToGroup() throws RemoteException {
		/*Si el grupo está creado entonces el nodo está conectado*/
		if(groupName != null)
			return true;
		return false;
	}

	@Override
	public void connectToGroup(String host, int port) throws RemoteException, NotBoundException {
		/*Aca no tengo toda la info del nodo al que me voy a conectar (host y puerto) me falta el id, 
		 * entonces addNewNode debería devolverme el nodo actual también*/

		/*Pido el objeto remoto del nodo al que me voy a conectar y agrego el nodo que se quiere conectar*/
		Registry remoteRegistry = LocateRegistry.getRegistry(host, port);
		ClusterAdministration stub = (ClusterAdministration)remoteRegistry.lookup(Node.CLUSTER_COMUNICATION);

		Set<NodeInformation> otherNodes = stub.addNewNode(this.currentNode.getInfo());

		for(NodeInformation it : otherNodes){
			if(!it.equals(currentNode.getInfo())){
				this.nodes.add(it);
			}
		}

		currentNode.getBalancer().bullyElection(currentNode.getInfo(), System.currentTimeMillis());

		System.out.println("El coordinador es " + currentNode.getCoordinator());
		
		getKnownNodes();
	}

	@Override
	public void disconnectFromGroup(NodeInformation node) throws RemoteException, NotBoundException {

		System.out.println("DISCONNECT!");
		
		/*Si soy yo mismo me agrego*/
		if(currentNode.getInfo().equals(node)){
			currentNode.interrupt();
			this.nodes.add(node);
		}

		if(!this.nodes.contains(node))
			return;
		else{
			this.nodes.remove(node);
			for(NodeInformation it : this.nodes){
				Registry remoteRegistry = LocateRegistry.getRegistry(it.host(), it.port());
				ClusterAdministration stub = (ClusterAdministration)remoteRegistry.lookup(Node.CLUSTER_COMUNICATION);
				stub.disconnectFromGroup(node);
			}
			
			System.out.println("Soy" + currentNode.getInfo() + "Mis nodos " + this.nodes);
		}
		System.out.println("DISCONNECT OUT");
		/*Si el nodo a borrar es el coordinador tengo que lanzar una eleccion*/
		if(currentNode.getCoordinator().equals(node)){
			System.out.println("***********************************************************************");
			/*Si yo soy el coordinador elijo a otro nodo para que me desconecten*/
			if(currentNode.getInfo().equals(node)){
				List<NodeInformation> otherNodes = new ArrayList<NodeInformation>(this.nodes);
				NodeInformation selectedNode = otherNodes.get(0);
				Registry remoteRegistry = LocateRegistry.getRegistry(selectedNode.host(), selectedNode.port());
				AgentsBalancer stub = (AgentsBalancer)remoteRegistry.lookup(Node.AGENTS_BALANCER);
				stub.bullyElection(selectedNode, System.currentTimeMillis());
			}
			else
				currentNode.getBalancer().bullyElection(currentNode.getInfo(), System.currentTimeMillis());
		}
	}

	@Override
	public Set<NodeInformation> addNewNode(NodeInformation node)
			throws RemoteException, NotBoundException {

		/*Primero veo si está ya este nodo o si no*/
		/*La segunda condición es para no agregarme a mi mismo a mi lista de conocidos*/
		if(nodes.contains(node) || node.equals(currentNode))
			return null;
		else
			this.nodes.add(node);

		Set<NodeInformation> iterator = new HashSet<NodeInformation>(nodes);
		/*Elimino el nodo que recién agregué para no iterar sobre él*/
		iterator.remove(node);

		/*Llamo a addNode de los nodos que conozco*/
		for(NodeInformation it : iterator){
			Registry remoteRegistry = LocateRegistry.getRegistry(it.host(), it.port());
			ClusterAdministration stub = (ClusterAdministration)remoteRegistry.lookup(Node.CLUSTER_COMUNICATION);
			stub.addNewNode(node);
		}

		Set<NodeInformation> ans = new HashSet<NodeInformation>(nodes);
		/*Agrego la info del nodo actual a la lista de nodos xq en connectToGroup no tengo
		 * el id del nodo*/
		ans.add(this.currentNode.getInfo());
		/*Elimino de la lista al que me pasaron para no agregarme a mi mismo después*/
		ans.remove(node);	

		return ans;
	}

	@Override
	public Set<NodeInformation> connectedNodes() throws RemoteException {
		return nodes;
	}

}
